package com.hougong.lineclient;

import com.hougong.lineclient.annotation.ProtocolField;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.util.CharsetUtil;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;

/**
 * 解析了 byte short int long 和 继承了BaseBodyCodec的单个类型 以及 BaseBodyCodec[]的数组类型 ，没有实现List模式 全用[]做数组类型
 *
 * @param <T>
 */
public class BaseBodyCodec<T extends BaseBodyCodec> implements BodyToBytes, BytesToBody<T> {
    @Override
    public byte[] toBytes() {
        ByteBuf byteBuf = Unpooled.buffer();
        try {
            Class<? extends BaseBodyCodec> clazz = this.getClass();
            Field[] declaredFields = clazz.getDeclaredFields();
            Arrays.sort(declaredFields, new Comparator<Field>() {
                @Override
                public int compare(Field o1, Field o2) {
                    if (!o1.isAnnotationPresent(ProtocolField.class)) {
                        return -9999;
                    }
                    if (!o2.isAnnotationPresent(ProtocolField.class)) {
                        return 9999;
                    }
                    int annotation1 = o1.getAnnotation(ProtocolField.class).sort();
                    int annotation2 = o2.getAnnotation(ProtocolField.class).sort();
                    return annotation1 - annotation2;
                }
            });

            for (Field field : declaredFields) {
                if (field.isAnnotationPresent(ProtocolField.class)) {
                    String fieldName = field.getName();
                    PropertyDescriptor pd = new PropertyDescriptor(fieldName, clazz);
                    Method readMethod = pd.getReadMethod();
                    Class<?> type = field.getType();
                    if (type == byte.class) {
                        byte invoke = (byte) readMethod.invoke(this);
                        byteBuf.writeByte(invoke);
                    }
                    if (type == short.class) {
                        short invoke = (short) readMethod.invoke(this);
                        byteBuf.writeShort(invoke);
                    }
                    if (type == int.class) {
                        int invoke = (int) readMethod.invoke(this);
                        byteBuf.writeInt(invoke);
                    }
                    if (type == long.class) {
                        long invoke = (long) readMethod.invoke(this);
                        byteBuf.writeLong(invoke);
                    }
                    if ((type.isArray() && type.getComponentType() == byte.class)) {
                        byte[] invoke = (byte[]) readMethod.invoke(this);
                        byteBuf.writeBytes(invoke);
                    }
                    if (type.isArray() && type.getComponentType().getSuperclass() == BaseBodyCodec.class) {
                        BaseBodyCodec[] o = (BaseBodyCodec[]) readMethod.invoke(this);
                        for (int i = 0; i < o.length; i++) {
                            byteBuf.retain();
                            byte[] bytes = o[i].toBytes();
                            byteBuf.writeBytes(bytes);
                        }
                    }
                    if (type == String.class) {
                        String invoke = (String) readMethod.invoke(this);
                        if(invoke!=null){
                            byteBuf.writeCharSequence(invoke, CharsetUtil.UTF_8);
                        }
                    }
                    if (type.getSuperclass() == BaseBodyCodec.class) {
                        BaseBodyCodec o = (BaseBodyCodec) readMethod.invoke(this);
                        byteBuf.retain();
                        byte[] o_bytes = o.toBytes();
                        byteBuf.writeBytes(o_bytes);
                    }
                }
            }
            byte[] bytes = new byte[byteBuf.readableBytes()];
            byteBuf.readBytes(bytes);
            return bytes;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            byteBuf.release();
        }
        return null;
    }

    @Override
    public T fillBodyPojo(ByteBuf byteBuf) {
        if (byteBuf != null) {
            try {
                Class<? extends BaseBodyCodec> clazz = this.getClass();
                Field[] declaredFields = clazz.getDeclaredFields();
                Arrays.sort(declaredFields, new Comparator<Field>() {
                    @Override
                    public int compare(Field o1, Field o2) {
                        if (!o1.isAnnotationPresent(ProtocolField.class)) {
                            return -9999;
                        }
                        if (!o2.isAnnotationPresent(ProtocolField.class)) {
                            return 9999;
                        }
                        int annotation1 = o1.getAnnotation(ProtocolField.class).sort();
                        int annotation2 = o2.getAnnotation(ProtocolField.class).sort();
                        return annotation1 - annotation2;
                    }
                });
                for (int i = 0; i < declaredFields.length; i++) {
                    Field field = declaredFields[i];
                    if (field.isAnnotationPresent(ProtocolField.class)) {
                        String fieldName = field.getName();
                        PropertyDescriptor pd = new PropertyDescriptor(fieldName, clazz);
                        Method writeMethod = pd.getWriteMethod();
                        Class<?> type = field.getType();
                        if (type == byte.class) {
                            writeMethod.invoke(this, byteBuf.readByte());
                        }
                        if (type == short.class) {
                            writeMethod.invoke(this, byteBuf.readShort());
                        }
                        if (type == int.class) {
                            writeMethod.invoke(this, byteBuf.readInt());
                        }
                        if (type == long.class) {
                            writeMethod.invoke(this, byteBuf.readLong());
                        }
                        if ((type.isArray() && type.getComponentType() == byte.class) || type == String.class) {
                            if (field.isAnnotationPresent(ProtocolField.class)) {
                                ProtocolField pf = field.getAnnotation(ProtocolField.class);
                                int len = pf.len();
                                String lenField = pf.lenField();
                                if (len != -1) {
                                    if (type == String.class) {
                                        CharSequence charSequence = byteBuf.readCharSequence(len, CharsetUtil.UTF_8);
                                        writeMethod.invoke(this, charSequence);
                                    } else {
                                        byte[] bytes = new byte[len];
                                        byteBuf.readBytes(bytes);
                                        writeMethod.invoke(this, bytes);
                                    }
                                } else if (!lenField.equals("")) {
                                    PropertyDescriptor len_field_pd = new PropertyDescriptor(lenField, clazz);
                                    Method readMethod = len_field_pd.getReadMethod();
                                    Integer integer = Integer.valueOf(readMethod.invoke(this).toString());
                                    if (type == String.class) {
                                        CharSequence charSequence = byteBuf.readCharSequence(integer, CharsetUtil.UTF_8);
                                        writeMethod.invoke(this, charSequence);
                                    } else {
                                        byte[] bytes = new byte[integer];
                                        byteBuf.readBytes(bytes);
                                        writeMethod.invoke(this, bytes);
                                    }
                                }
                            } else {
                                throw new RuntimeException(fieldName + " 没有@ProtocolField注解！！");
                            }
                        }
                        //对于BaseBodyCodec[]类型的 如果没有指定名长度则 一定是要放在最后 因为后面只能根据判断后面是否还存在可读字节
                        //来处理是否继续读取 一直循环下去
                        if (type.isArray() && type.getComponentType().getSuperclass() == BaseBodyCodec.class) {
                            Class componentType = type.getComponentType();
                            if (field.isAnnotationPresent(ProtocolField.class)) {
                                int itemLen = 0;
                                ProtocolField pf = field.getAnnotation(ProtocolField.class);
                                int len = pf.len();
                                String lenField = pf.lenField();
                                if (len != -1) {
                                    itemLen = len;
                                } else if (!lenField.equals("")) {
                                    PropertyDescriptor len_field_pd = new PropertyDescriptor(lenField, clazz);
                                    Method readMethod = len_field_pd.getReadMethod();
                                    itemLen = Integer.valueOf(readMethod.invoke(this).toString());
                                }
                                Object o = Array.newInstance(componentType, itemLen);
                                for (int j = 0; j < itemLen; j++) {
                                    BaseBodyCodec item_o = (BaseBodyCodec) componentType.newInstance();
                                    byteBuf.retain();
                                    item_o.fillBodyPojo(byteBuf);
                                    Array.set(o, j, item_o);
                                }
                                writeMethod.invoke(this, o);
                            } else {
                                //判断是不是最后一项
                                if (i == declaredFields.length - 1) {
                                    ArrayList tmpList = new ArrayList();
                                    while (byteBuf.isReadable()) {
                                        BaseBodyCodec item_o = (BaseBodyCodec) componentType.newInstance();
                                        byteBuf.retain();
                                        item_o.fillBodyPojo(byteBuf);
                                        tmpList.add(item_o);
                                    }
                                    Object[] objects = tmpList.toArray();
                                    Object o = Array.newInstance(componentType, objects.length);
                                    for (int j = 0; j < objects.length; j++) {
                                        Array.set(o, j, objects[j]);
                                    }
                                    writeMethod.invoke(this, o);
                                } else {
                                    throw new RuntimeException(fieldName + " 没有注解而且不是最后一项无法正常解析！！");
                                }
                            }
                        }
                        if (type.getSuperclass() == BaseBodyCodec.class) {
                            BaseBodyCodec o = (BaseBodyCodec) type.newInstance();
                            writeMethod.invoke(this, o);
                            byteBuf.retain();
                            o.fillBodyPojo(byteBuf);
                        }
                    }
                }
                return (T) this;
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                byteBuf.release();
            }
        }
        return null;
    }

    @Override
    public void initParam() {

    }
}
